#pragma once

#include "znx_conn.h"
#include "comm/znx_string.h"
#include "comm/znx_array.h"
#include "comm/znx_pool.h"
#include "comm/znx_crc.h"
#include "comm/znx_rwlock.h"

#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/bn.h>
#include <openssl/conf.h>
#include <openssl/crypto.h>
#include <openssl/dh.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#ifndef OPENSSL_NO_OCSP
#include <openssl/ocsp.h>
#endif
#include <openssl/rand.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>


#if (defined LIBRESSL_VERSION_NUMBER && OPENSSL_VERSION_NUMBER == 0x20000000L)
#undef OPENSSL_VERSION_NUMBER
#if (LIBRESSL_VERSION_NUMBER >= 0x2080000fL)
#define OPENSSL_VERSION_NUMBER          0x1010000fL
#else
#define OPENSSL_VERSION_NUMBER          0x1000107fL
#endif
#endif


#if (OPENSSL_VERSION_NUMBER >= 0x10100001L)
#define znx_ssl_version()               OpenSSL_version(OPENSSL_VERSION)
#else
#define znx_ssl_version()               SSLeay_version(SSLEAY_VERSION)
#endif


#if (OPENSSL_VERSION_NUMBER < 0x10002000L)
#define SSL_is_server(s)                (s)->server
#endif


#define ZNX_SSL_BUFSIZE                 16384
#define ZNX_SSL_MAX_SESSION_SIZE        4096


#define ZNX_SSL_SSLv2                   0x0002
#define ZNX_SSL_SSLv3                   0x0004
#define ZNX_SSL_TLSv1                   0x0008
#define ZNX_SSL_TLSv1_1                 0x0010
#define ZNX_SSL_TLSv1_2                 0x0020
#define ZNX_SSL_TLSv1_3                 0x0040


typedef struct znx_ssl_conf_s           znx_ssl_conf_t;
typedef struct znx_ssl_conn_s           znx_ssl_conn_t;


struct znx_ssl_conf_s {
    void                                *data;
    SSL_CTX                             *ctx;
    STACK_OF(X509)                      *chain;
    size_t                              buffer_size;
    znx_cleanup_list_t                  cleanup;
    znx_array_t                         *passwords;

    znx_rwlock_t                        rwlock;
    znx_array_t                         *ticket_keys;
};


struct znx_ssl_conn_s {
    SSL                                 *ssl;
    SSL_CTX                             *ssl_ctx;
    SSL_SESSION                         *session;

    znx_result_t                        last;
    znx_conn_handle_pt                  handler;

    znx_event_handler_t                 saved_read_handler;
    znx_event_handler_t                 saved_write_handler;

    int64_t                             handshake_start_ms;
    int64_t                             handshake_end_ms;

    unsigned                            handshaked:1;
    unsigned                            renegotiation:1;
    unsigned                            buffer:1;
    unsigned                            no_wait_shutdown:1;
    unsigned                            no_send_shutdown:1;
    unsigned                            handshake_buffer_set:1;
};


typedef struct {
    int64_t                             expire_ms;
    uint32_t                            crc;
    u_char                              id[32];
    size_t                              id_len;
    u_char                              *session;
    size_t                              session_len;
} znx_ssl_session_id_t;


#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB

typedef struct {
    uint32_t                            size;
    u_char                              name[16];
    u_char                              hmac_key[32];
    u_char                              aes_key[32];
} znx_ssl_session_ticket_key_t;

#endif


static inline znx_ssl_session_id_t *
znx_ssl_session_id_create(u_char *id, size_t id_len,
    u_char *session, size_t session_len, int64_t expire_ms)
{
    znx_ssl_session_id_t        *session_id;
    session_id = znx_thread_calloc(sizeof(znx_ssl_session_id_t) + session_len);
    session_id->session = (u_char *)(session_id) + sizeof(znx_ssl_session_id_t);
    znx_memcpy(session_id->id, id, id_len);
    session_id->id_len = id_len;
    session_id->crc = znx_crc32_long(id, id_len);
    znx_memcpy(session_id->session, session, session_len);
    session_id->session_len = session_len;
    session_id->expire_ms = expire_ms;

    return session_id;
}


static inline void
znx_ssl_session_id_destroy(znx_ssl_session_id_t *session_id)
{
    znx_thread_free(session_id);
}


static inline int
znx_ssl_session_id_cmp(void *a, void *b) {
    znx_ssl_session_id_t *session_id_a = a;
    znx_ssl_session_id_t *session_id_b = b;

    if (session_id_a->crc > session_id_b->crc) {
        return 1;
    }

    if (session_id_a->crc < session_id_b->crc) {
        return -1;
    }

    if (session_id_a->id_len > session_id_b->id_len) {
        return 1;
    }

    if (session_id_a->id_len < session_id_b->id_len) {
        return -1;
    }

    return znx_strncmp((char *)session_id_a->id,
        (char *)session_id_b->id, session_id_a->id_len);
}


znx_bool_t znx_ssl_init();

znx_ssl_conf_t *znx_ssl_conf_create(uint64_t protocols,
    void *data, char **err);
void znx_ssl_conf_destroy(znx_ssl_conf_t *ssl_conf);


STACK_OF(X509) *znx_parse_pem_cert(const u_char *pem,
    size_t pem_len, char **err);
void znx_free_cert(STACK_OF(X509) *chain);


EVP_PKEY *znx_parse_pem_priv_key(const u_char *pem_key,
    size_t pem_key_len, znx_array_t *passwords, char **err);
void znx_free_priv_key(EVP_PKEY *pkey);


// After calling znx_ssl_conf_set_cert, the chain will be managerd by ssl_conf
// and released after ssl_conf is destroyed.
znx_bool_t znx_ssl_conf_set_cert(znx_ssl_conf_t *ssl_conf, STACK_OF(X509) *chain, char **err);
znx_bool_t znx_ssl_set_cert(SSL *ssl, STACK_OF(X509) *chain, char **err);
znx_bool_t znx_ssl_conn_set_chain(znx_ssl_conn_t *conn,
    STACK_OF(X509) * chain, char **err);
znx_bool_t znx_ssl_conf_set_priv_key(znx_ssl_conf_t *ssl_conf, EVP_PKEY *pkey, char **err);
znx_bool_t znx_ssl_set_priv_key(SSL *ssl, EVP_PKEY *pkey, char **err);
znx_bool_t znx_ssl_conn_set_priv_key(znx_ssl_conn_t *conn,
    EVP_PKEY *pkey, char **err);


znx_bool_t znx_ssl_ciphers(znx_ssl_conf_t *ssl_conf, const u_char *ciphers,
    znx_bool_t prefer_server_ciphers, char **err);

znx_bool_t znx_ssl_client_certificate_set(znx_ssl_conf_t *ssl_conf,
    STACK_OF(X509) *ca_certs, char **err);


// the difference between ssl_client_certificate and ssl_trusted_certificate.
// ssl_client_certificate 会将信任的CA列表发送给客户端, 而ssl_trusted_certificate不会.
znx_bool_t znx_ssl_client_certificate(znx_ssl_conf_t *ssl_conf,
    znx_str_t *cert_buf, int depth, char **err);
znx_bool_t znx_ssl_trusted_certificate(znx_ssl_conf_t *ssl_conf,
    znx_str_t *cert_str, int depth, char **err);


znx_bool_t znx_ssl_crl(znx_ssl_conf_t *ssl_conf, znx_str_t *crl, char **err);
znx_bool_t znx_ssl_dhparam(znx_ssl_conf_t *ssl_conf, znx_str_t *file, char **err);
znx_bool_t znx_ssl_ecdh_curve(znx_ssl_conf_t *ssl_conf, znx_str_t *name, char **err);


// commands elements is znx_keyval_t
znx_bool_t znx_ssl_conf_commands(znx_ssl_conf_t *ssl_conf, znx_array_t *commands, char **err);


void znx_ssl_session_cache_init(int64_t max_items);
void znx_ssl_session_cache_put(znx_ssl_session_id_t *session_id);
SSL_SESSION *znx_ssl_session_cache_get(znx_ssl_session_id_t *target);
void znx_ssl_session_cache_remove(znx_ssl_session_id_t *target);
void znx_ssl_session_cache_destroy();
void znx_ssl_remove_cached_session(SSL_CTX *ssl_ctx, SSL_SESSION *ssl_session);
znx_bool_t znx_ssl_session_cache(znx_ssl_conf_t *ssl_conf, znx_str_t *sess_ctx,
    znx_bool_t enable, int64_t timeout_ms, char **err);


znx_bool_t znx_ssl_session_ticket_key(znx_ssl_conf_t *ssl_conf,
    znx_str_t *key, char **err);


znx_bool_t znx_ssl_create_connection(znx_ssl_conf_t *ssl_conf, znx_conn_t *c,
    znx_bool_t client, char **err);


znx_bool_t znx_ssl_set_session(znx_conn_t *c, SSL_SESSION *ssl_session, char **err);

SSL_SESSION *znx_ssl_get_session(znx_conn_t *c);
SSL_SESSION *znx_ssl_get0_session(znx_conn_t *c);

#define znx_ssl_free_session            SSL_SESSION_free


#define znx_ssl_verify_error_optional(n)                        \
    (n == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT                \
     || n == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN               \
     || n == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY       \
     || n == X509_V_ERR_CERT_UNTRUSTED                          \
     || n == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE)


znx_bool_t znx_ssl_check_host(znx_conn_t *c, znx_str_t *name);


static inline const char *
znx_ssl_get_protocol(znx_conn_t *c)
{
    return SSL_get_version(c->ssl->ssl);
}


static inline const char *
znx_ssl_get_cipher_name(znx_conn_t *c)
{
    return SSL_get_cipher_name(c->ssl->ssl);
}


znx_bool_t znx_ssl_get_ciphers(znx_conn_t *c, znx_pool_t *pool, znx_str_t *s);
znx_bool_t znx_ssl_get_curves(znx_conn_t *c, znx_pool_t *pool, znx_str_t *s);


static inline znx_bool_t
znx_ssl_session_reused(znx_conn_t *c)
{
    if (SSL_session_reused(c->ssl->ssl)) {
        return ZNX_TRUE;
    }

    return ZNX_FALSE;
}


znx_bool_t znx_ssl_get_server_name(znx_conn_t *c,
    znx_str_t *s, char **err);

// in openssl, sni parse behind client_hello_cb.
// therefore we provide a function that resolves at the client_hello_cb.
znx_bool_t znx_ssl_get_client_hello_server_name(znx_conn_t *c,
    znx_str_t *s, char **err);


znx_bool_t znx_ssl_get_raw_certificate(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err);
znx_bool_t znx_ssl_get_subject_dn(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err);
znx_bool_t znx_ssl_get_issuer_dn(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err);
znx_bool_t znx_ssl_get_issuer_dn(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err);
znx_bool_t znx_ssl_get_subject_dn_legacy(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err);
znx_bool_t znx_ssl_get_issuer_dn_legacy(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err);
znx_bool_t znx_ssl_get_serial_number(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err);
znx_bool_t znx_ssl_get_fingerprint(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err);
void znx_ssl_get_client_verify(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s);
znx_bool_t znx_ssl_get_client_v_start(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err);
znx_bool_t znx_ssl_get_client_v_end(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err);
znx_bool_t znx_ssl_get_client_v_remain(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err);


znx_bool_t znx_ssl_ocsp_get_status(znx_conn_t *c, const char **s);


static inline int64_t
znx_ssl_get_handshake_time_ms(znx_conn_t *c)
{
    if (c->ssl == NULL) {
        return 0;
    }

    int64_t end_ms = c->ssl->handshake_end_ms;
    int64_t start_ms = c->ssl->handshake_start_ms;

    int64_t ret = end_ms - start_ms;
    if (ret < 0) {
        return 0;
    }

    return ret;
}


znx_result_t znx_ssl_handshake(znx_conn_t *c);
znx_result_t znx_ssl_shutdown(znx_conn_t *c);


// Notice:
// SSL_write: https://www.openssl.org/docs/man1.1.1/man3/SSL_write.html
// 在任意时刻SSL都可能发起重新协商, 因此在SSL_write期间可能返回WANT_READ错误.
// SSL_read: https://www.openssl.org/docs/man1.1.1/man3/SSL_read.html
// 因为SSL在任意时刻可能发送一些非应用的数据, 因此在SSL_read期间可能返回WANT_WRITE错误.
// 当上述两种情况发送时, 我们需要修改conn相关的event handle, 从而实现
// 在read/write event ready的情况下继续执行SSL_write/SSL_read.
// 但是上层状态机在任意时刻, 都可能注册/移除读写事件.
// 两者如果都操作conn->read/write handle, 并执行enable/disable操作, 可能导致回调逻辑发生错误.
// 因此, 上层状态机, 在进行SSL相关数据读写时, 应该采用如下的方式.
// 1. 在握手完毕后(读写之前), 上层状态机应该为conn->read/wrtie event绑定一个后续不发生变化的回调函数.
// 由该回调函数驱动http状态机的运行.
// 2. 因为在SSL_read/write期间, 可能注册写事件回调和读事件回调, 上层状态机在没有读取或写入数据时, 都可能收到对应的事件回调.
// 因此上层状态机的读写事件回调, 需要具备在不期望读/写的场景下, 收到对应事件后, 对对应的事件执行disable的能力.
// 3. 上层状态机在执行disable event操作, 不应该影响到ssl的使用. disable被设计为异步触发的.
// 
// eg: znx_http_request
// 
// znx_http_request结构中新增了 read_event_handle, write_event_handle, read_event_active, write_event_active.
// 在创建request之后, 我们就将conn->read/write.handle设置为znx_http_event_handle,
// http状态机需要修改handle时, 可安全的修改 request->read_event_handle/write_event_handle.
// 当conn上的read/write事件就绪时, 将回调znx_http_event_handle.
// 在 znx_http_event_handle中, 它会根据request->read_event_active/write_event_active来决定是否要透传该事件.
// 如果事件不允许被透传, 则对该事件执行disable操作. 反之则根据事件类型回调 request->read_event_handle/write_event_handle.
// http中, 不应该直接使用 znx_event_loop_enable/disable_read/write接口. 而是znx_http_enable/disable_read/write.
// znx_http_enable/disable_read/write 会修改read/write_event_active标志, 它会调用event_loop_enable, 但是不会调用event_loop_disable接口.
// event_loop_disable接口由znx_http_event_handle按需调用.
//
// 在znx_ssl_read/znx_ssl_write中, 它可能修改conn event handle, 并监听对应的事件, 从而继续驱动执行SSL_read/SSL_write.
// 并且在使用完毕后, 会将conn event handle重新恢复回去.


znx_result_t znx_ssl_write(znx_conn_t *c, void *buff, size_t count);

znx_result_t znx_ssl_writev(znx_conn_t *conn, znx_iovec_t *vector, int count);

znx_result_t znx_ssl_read(znx_conn_t *c, void *buff, size_t count);

znx_result_t znx_ssl_readv(znx_conn_t *conn, znx_iovec_t *vector, int count);


extern int  znx_ssl_conn_index;
extern int  znx_ssl_conf_index;

#define znx_ssl_get_conn(ssl)                           \
    SSL_get_ex_data(ssl, znx_ssl_conn_index)

